package com.atguigu.guli.service.ucenter.service.impl;

import com.atguigu.guli.service.base.handler.GuliException;
import com.atguigu.guli.service.base.result.ResultCodeEnum;
import com.atguigu.guli.service.base.utils.JwtHelper;
import com.atguigu.guli.service.base.utils.JwtInfo;
import com.atguigu.guli.service.ucenter.controller.util.WxOpenProperties;
import com.atguigu.guli.service.ucenter.entity.Member;
import com.atguigu.guli.service.ucenter.entity.form.ApiMemberForm;
import com.atguigu.guli.service.ucenter.entity.form.LoginForm;
import com.atguigu.guli.service.ucenter.mapper.MemberMapper;
import com.atguigu.guli.service.ucenter.service.MemberService;
import com.atguigu.utils.FormUtils;
import com.atguigu.utils.HttpClientUtils;
import com.atguigu.utils.MD5;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.ParseException;
import java.util.Map;
import java.util.UUID;

/**
 * <p>
 * 会员表 服务实现类
 * </p>
 *
 * @author atguigu
 * @since 2021-12-10
 */
@Service
public class MemberServiceImpl extends ServiceImpl<MemberMapper, Member> implements MemberService {
    final String encoded = "$.-+=";
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    WxOpenProperties wxOpenProperties ;

    @Override
    public void saveMember(ApiMemberForm apiMemberForm) {
        //检查验证码：
        /*
            验证码：
                    防止表单重复提交
                    防止恶意攻击
                    解决高并发问题
         */
        String code = apiMemberForm.getCode();
        String password = apiMemberForm.getPassword();
        String mobile = apiMemberForm.getMobile();
        String nickname = apiMemberForm.getNickname();
        if (StringUtils.isEmpty(code) || //请求参数错误
                StringUtils.isEmpty(password) ||
                StringUtils.isEmpty(mobile) ||
                StringUtils.isEmpty(nickname) ||
                !FormUtils.isMobile(mobile)
        ) {
            throw new GuliException(ResultCodeEnum.PARAM_ERROR);
        }
        //验证验证码
        String codeKey = "sms:regist:code:" + mobile;//
        Object codeObj = redisTemplate.opsForValue().get(codeKey);
        if (codeObj == null || !codeObj.toString().equals(code)) {//验证码错误或者表单重复提交
            throw new GuliException(ResultCodeEnum.CODE_ERROR);
        }
        //执行注册的业务
        //判断手机号码是否被占用
        LambdaQueryWrapper<Member> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Member::getMobile, mobile);
        if (baseMapper.selectCount(queryWrapper) > 0) {
            throw new GuliException(ResultCodeEnum.REGISTER_MOBLE_ERROR);
        }

        Member member = new Member();
        BeanUtils.copyProperties(apiMemberForm, member);
        member.setAvatar("https://sh210726leo-guli-file.oss-cn-shanghai.aliyuncs.com/avatar/2021/11/29/default.jpg");
        //密码需要加密
        // md5加密的结果可以被暴力破解：加盐保证数据安全
        member.setPassword(MD5.encrypt(apiMemberForm.getPassword() + encoded));
        baseMapper.insert(member);

        redisTemplate.delete(codeKey);//删除redis中缓存的验证码

    }

    @Override
    public String login(LoginForm loginForm) {
        //1.校验手机号
        LambdaQueryWrapper<Member> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Member::getMobile, loginForm.getMobile());
        Member member = baseMapper.selectOne(queryWrapper);
        if (member == null) {
            //手机号未注册
            throw new GuliException(ResultCodeEnum.LOGIN_PHONE_ERROR);
        }
        //2.校验密码
        if (!MD5.encrypt(loginForm.getPassword() + encoded).equals(member.getPassword())) {
            //密码错误
            throw new GuliException(ResultCodeEnum.LOGIN_PASSWORD_ERROR);
        }
        //3.生成jwt
        JwtInfo jwtInfo = new JwtInfo(
                member.getId(),
                member.getNickname(),
                member.getAvatar()
        );

        String token = JwtHelper.createToken(jwtInfo);
        return token;
    }

    @Override
    public String wxLogin(HttpSession session) {
         try {
            //拼接wx二维码链接的获取地址  重定向响应给浏览器
            String url = "https://open.weixin.qq.com/connect/qrconnect?" +
                    "appid=%s" + //当前应用在wx中注册的id
                    "&redirect_uri=%s" + //回调接口的地址
                    "&response_type=code" +
                    "&scope=snsapi_login" +
                    "&state=%s" + //防止回调接口被恶意攻击
                    "#wechat_redirect";
            String state = UUID.randomUUID().toString().replace("-", "");
            session.setAttribute("state", state);//缓存到服务器
            url = String.format(url, wxOpenProperties.getAppId(),
                    URLEncoder.encode(wxOpenProperties.getRedirectUri(), "UTF-8"),
                    state);
            return "redirect:" + url;
        } catch (Exception e) {
            throw new GuliException(ResultCodeEnum.UNKNOWN_REASON, e);
        }
    }

    @Override
    public String callback(String code, String state, HttpSession session) {
        //判断本次请求是不是恶意攻击
        try {
            Object sessionState = session.getAttribute("state1");
            if(sessionState==null || !sessionState.toString().equals(state)){
                throw new GuliException(ResultCodeEnum.ILLEGAL_CALLBACK_REQUEST_ERROR);
            }
            //根据code:临时票据  获取wx用户的数据
            //1、使用code获取accessToken(包含用户授权+当前项目身份的授权token)
            String url = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
                    "appid=" + wxOpenProperties.getAppId()+
                    "&secret=" +wxOpenProperties.getAppSecret()+
                    "&code=" + code +
                    "&grant_type=authorization_code";
            //自己的代码中发起请求
            //创建客户端对象
            HttpClientUtils client = new HttpClientUtils(url);
            //发起请求
            client.get();
            //获取结果
            String content = client.getContent();
            Gson gson = new Gson();
            Map map = gson.fromJson(content, Map.class);

            if(map.get("errcode")!=null){
                throw new GuliException(ResultCodeEnum.ILLEGAL_CALLBACK_REQUEST_ERROR);
            }
            //请求成功：获取accessToken+openid
            String access_token = map.get("access_token").toString();
            String openid = map.get("openid").toString(); //openid代表wx唯一用户
            //从数据库中查询openid对应的用户  如果有代表该wx用户之前使用wx登录过，直接返回数据库wx用户的信息
            LambdaQueryWrapper<Member> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Member::getOpenid , openid);
            Member member = this.getOne(queryWrapper);
            if(member==null){
                //如果openid没有查询到数据库中的用户，代表该wx用户第一次使用wx登录
                //2、使用token和openid获取wx用户的信息
                url = "https://api.weixin.qq.com/sns/userinfo?" +
                        "access_token=" + access_token+
                        "&openid="+ openid;
                client = new HttpClientUtils(url);
                client.get();
                content = client.getContent();
                map = gson.fromJson(content,Map.class);
                if(map.get("errcode")!=null){
                    throw new GuliException(ResultCodeEnum.ILLEGAL_CALLBACK_REQUEST_ERROR);
                }
                String nickname = map.get("nickname").toString();
                String sex = map.get("sex").toString();// 0.0  gson将json中的整形数值转为map中数据时 会将它转为double类型的值
                String headimgurl = map.get("headimgurl").toString();

                //将wx用户转为我们自己的用户存到数据库中
                member = new Member();
                member.setAvatar(headimgurl);
                member.setOpenid(openid);
                member.setNickname(nickname);
                member.setSex((int)(Double.parseDouble(sex)));
                this.save(member);//雪花算法自动生成id
            }else if((System.currentTimeMillis()-member.getGmtModified().getTime())>=7*24*60*60*1000){//如果用户离上次更新的时间相差了7天，重新查询wx用户信息更新数据库中的数据
                //重新查询wx用户的数据更新
                //如果openid没有查询到数据库中的用户，代表该wx用户第一次使用wx登录
                //2、使用token和openid获取wx用户的信息
                url = "https://api.weixin.qq.com/sns/userinfo?" +
                        "access_token=" + access_token+
                        "&openid="+ openid;
                client = new HttpClientUtils(url);
                client.get();
                content = client.getContent();
                map = gson.fromJson(content,Map.class);
                if(map.get("errcode")!=null){
                    throw new GuliException(ResultCodeEnum.ILLEGAL_CALLBACK_REQUEST_ERROR);
                }
                String nickname = map.get("nickname").toString();
                String sex = map.get("sex").toString();// 0.0  gson将json中的整形数值转为map中数据时 会将它转为double类型的值
                String headimgurl = map.get("headimgurl").toString();

                member.setAvatar(headimgurl);
                member.setNickname(nickname);
                member.setSex((int)(Double.parseDouble(sex)));
                this.updateById(member);//更新用户
            }
            //wx用户数据缓存的时间不宜太长，避免用户修改了个人信息
            //将用户信息创建为jwt
            JwtInfo jwtInfo = new JwtInfo();
            jwtInfo.setAvatar(member.getAvatar());
            jwtInfo.setNickname(member.getNickname());
            jwtInfo.setId(member.getId());
            String token = JwtHelper.createToken(jwtInfo);
            return "redirect:http://localhost:3000?token="+token;
        } catch (Exception e) {
            //throw new GuliException(ResultCodeEnum.ILLEGAL_CALLBACK_REQUEST_ERROR, e);
            //跳转到错误页面显示错误信息
            try {
                return "redirect:http://localhost:3000/error?msg="+URLEncoder.encode(ResultCodeEnum.ILLEGAL_CALLBACK_REQUEST_ERROR.getMessage(),"UTF-8");
            } catch (UnsupportedEncodingException ex) {
                throw new GuliException(ResultCodeEnum.ILLEGAL_CALLBACK_REQUEST_ERROR);
            }
        }
    }
}
